#include "cexceptions/.TryCatchContextStack.h"
#include "cexceptions/.TryCatchContext.h"

TryCatchContextStack_t TryCatchContextStack_default(){
  return &_TryCatchContextStack_default;
}
TryCatchContextStack_t TryCatchContextStack_allocate(size_t count){
  return malloc(sizeof(struct _TryCatchContextStack)*count);
}
TryCatchContextStack_t TryCatchContextStack_construct(TryCatchContextStack_t this){
  assert(this && "Null given as this-argument");
  *this=(struct _TryCatchContextStack){0};
  return this;
}
TryCatchContextStack_t TryCatchContextStack_destruct(TryCatchContextStack_t this){
  assert(this && "Null given as this-argument");
  while(this->head){
    TryCatchContextStack_pop(this);
  }
  return this;
}
struct __jmp_buf_tag * TryCatchContextStack_push(TryCatchContextStack_t this){
  assert(this && "NULL given as TryCatchContextStack argument");
  struct _TryCatchContext value={.parent=this->head,.handle={{}},.exception={0}};
  TryCatchContext_t allocation=calloc(sizeof(struct _TryCatchContext), 1);
  assert(allocation && "TryCatchContext allocation failed");
  *allocation=value;
  this->head=allocation;
  return allocation->handle;
}
TryCatchContextStack_t TryCatchContextStack_pop(TryCatchContextStack_t this){
  assert(this && "NULL given as TryCatchContextStack argument\n");
  assert(this->head && "Given TryCatchContextStack is empty\n");
  TryCatchContext_t popped=this->head;
  Throwable_destruct(&(popped)->exception);
  this->head=popped->parent;
  free(popped);
  return this;
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winfinite-recursion"

void _TryCatchContextStack_throw(TryCatchContextStack_t this, struct _Throwable exception){
  assert(exception.type && "Exception type must be non-zero\n");
  if(!this->head){
    fprintf(
      stderr,
      "[Unhandled exception] %T\n",
      &exception
    );
    exit(EXIT_FAILURE);
  }
  if(!this->head->exception.type){
    this->head->exception=exception;
    longjmp(this->head->handle, exception.type);
  }else{
    TryCatchContextStack_pop(this);
    _TryCatchContextStack_throw(this, exception);
  }
}

#pragma GCC diagnostic pop

void TryCatchContextStack_throw(TryCatchContextStack_t this, int type, char * message, int code, Throwable_t previous, char * file, int line){
  assert(type && "Exception type must be non-zero\n");
  struct _Throwable exception;
  Throwable_construct(&exception, type, message, code, previous, file, line);
  _TryCatchContextStack_throw(this, exception);
}

void TryCatchContextStack_propagate(TryCatchContextStack_t this){
  assert(this && "NULL given as TryCatchContext reference argument\n");
  assert(this->head && this->head->exception.type && "Nothing to propagate");
  TryCatchContextStack_throw(this, this->head->exception.type, this->head->exception.message, this->head->exception.code, this->head->exception.previous, this->head->exception.file, this->head->exception.line);;
}

Throwable_t TryCatchContextStack_getException(TryCatchContextStack_t this){
  assert(this && "NULL given as TryCatchContext reference argument\n");
  assert(this->head->exception.type && "There is no exception\n");
  return &this->head->exception;
}